package com.wxsoft.java.thread.safety;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 上面例子很容易理解，有一张银行卡，里面有1000的余额，程序模拟你和你老婆同时在取款机进行取钱操作的场景。多次运行此程序，可能具有多个不同组合的输出结果。
 * 其中一种可能的输出为：
 * 
 * 1 取钱成功， 取出钱数为：700.0 
 * 2 余额为：300.0 
 * 3 取钱成功， 取出钱数为：700.0 
 * 4 余额为：-400.0
 * 也就是说，对于一张只有1000余额的银行卡，你们一共可以取出1400，这显然是有问题的。
 * 
 * 经过分析，问题在于Java多线程环境下的执行的不确定性。CPU可能随机的在多个处于就绪状态中的线程中进行切换，因此，很有可能出现如下情况：
 * 当thread1执行到
 * //1处代码时，判断条件为true，此时CPU切换到thread2，执行//1处代码，发现依然为真，然后执行完thread2，接着切换到thread1
 * ，接着执行完毕。此时，就会出现上述结果。
 * 
 * 因此，讲到线程安全问题，其实是指多线程环境下对共享资源的访问可能会引起此共享资源的不一致性。因此，为避免线程安全问题，
 * 应该避免多线程环境下对此共享资源的并发访问。
 * 
 * 解决办法：
 * 1. 同步方法
 * 对共享资源进行访问的方法定义中加上synchronized关键字修饰，使得此方法称为同步方法。可以简单理解成对此方法进行了加锁，其锁对象为当前方法所在的对象自身。多线程环境下，当执行此方法时，首先都要获得此同步锁（且同时最多只有一个线程能够获得），只有当线程执行完此同步方法后，才会释放锁对象，其他的线程才有可能获取此同步锁，以此类推...
 * 
 * 2. 同步代码块
 * 正如上面所分析的那样，解决线程安全问题其实只需限制对共享资源访问的不确定性即可。使用同步方法时，使得整个方法体都成为了同步执行状态，会使得可能出现同步范围过大的情况，于是，针对需要同步的代码可以直接另一种同步方式——同步代码块来解决。
 * 
 * 3. Lock对象同步锁
 * 
 * @author chenliang
 *
 */
public class ThreadTest {

	public static void main(String[] args) {
		Account account = new Account("123456", 1000);
		DrawMoneyRunnable drawMoneyRunnable = new DrawMoneyRunnable(account,
				700);
		Thread myThread1 = new Thread(drawMoneyRunnable);
		Thread myThread2 = new Thread(drawMoneyRunnable);
		myThread1.start();
		myThread2.start();
	}

}

class DrawMoneyRunnable implements Runnable {

	private Account account;
	private double drawAmount;

	public DrawMoneyRunnable(Account account, double drawAmount) {
		super();
		this.account = account;
		this.drawAmount = drawAmount;
	}

//	public void run() {
//		// 盘点余额 是否 充足
//		System.out.println("当前余额："+ account.getBalance());
//		if (account.getBalance() >= drawAmount) { // 1
//			System.out.println("取钱成功， 取出钱数为：" + drawAmount);
//			double balance = account.getBalance() - drawAmount;
//			account.setBalance(balance);
//			System.out.println("余额为：" + balance);
//		}
//	}
	/**
	 * 1 对共享资源进行访问的方法定义中加上synchronized关键字修饰，使得此方法称为同步方法。可以简单理解成对此方法进行了加锁，其锁对象为当前方法所在的对象自身。多线程环境下，当执行此方法时，首先都要获得此同步锁（且同时最多只有一个线程能够获得），只有当线程执行完此同步方法后，才会释放锁对象，其他的线程才有可能获取此同步锁，以此类推...
	 */
//	public synchronized void run() {
//		// 盘点余额 是否 充足
//		System.out.println("当前余额："+ account.getBalance());
//		if (account.getBalance() >= drawAmount) { // 1
//			System.out.println("取钱成功， 取出钱数为：" + drawAmount);
//			double balance = account.getBalance() - drawAmount;
//			account.setBalance(balance);
//			System.out.println("余额为：" + balance);
//		}
//		
//	}
	/**
	 * 2 正如上面所分析的那样，解决线程安全问题其实只需限制对共享资源访问的不确定性即可。使用同步方法时，使得整个方法体都成为了同步执行状态，会使得可能出现同步范围过大的情况，于是，针对需要同步的代码可以直接另一种同步方式——同步代码块来解决。
	 */
//	public void run() {
//		synchronized (account) {
//			// 盘点余额 是否 充足
//			if (account.getBalance() >= drawAmount) { // 1
//				System.out.println("取钱成功， 取出钱数为：" + drawAmount);
//				double balance = account.getBalance() - drawAmount;
//				account.setBalance(balance);
//				System.out.println("余额为：" + balance);
//			}
//		}
//	}
	// 显示定义Lock同步锁对象，此对象与共享资源具有一对一关系
	private final Lock lock = new ReentrantLock();
	/**
	 * 3 使用Lock对象同步锁可以方便的解决此问题，唯一需要注意的一点是Lock对象需要与资源对象同样具有一对一的关系。
	 */
	public void run() {
		// 加锁
		lock.lock();

		// 盘点余额 是否 充足
		if (account.getBalance() >= drawAmount) { // 1
			System.out.println("取钱成功， 取出钱数为：" + drawAmount);
			double balance = account.getBalance() - drawAmount;
			account.setBalance(balance);
			System.out.println("余额为：" + balance);
		}
		// 释放Lock锁
		lock.unlock();
	}
}

/**
 * 账户
 * 
 * @author chenliang
 *
 */
class Account {

	private String accountNo;
	private double balance;

	public Account() {

	}

	public Account(String accountNo, double balance) {
		this.accountNo = accountNo;
		this.balance = balance;
	}

	public String getAccountNo() {
		return accountNo;
	}

	public void setAccountNo(String accountNo) {
		this.accountNo = accountNo;
	}

	public double getBalance() {
		return balance;
	}

	public void setBalance(double balance) {
		this.balance = balance;
	}

}